package com.bugsnag.ohos;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Stores breadcrumbs added to the [Client] in a ring buffer. If the number of breadcrumbs exceeds
 * the maximum configured limit then the oldest breadcrumb in the ring buffer will be overwritten.
 * <p>
 * When the breadcrumbs are required for generation of an event a [List] is constructed and
 * breadcrumbs added in the order of their addition.
 *
 * @since 2021-07-02
 */
public class BreadcrumbState extends BaseObservable implements JsonStream.Streamable {
    private int maxBreadcrumbs;
    private CallbackState callbackState;
    private Logger logger;
    private int validIndexMask = Integer.MAX_VALUE;
    private Breadcrumb[] store;
    private AtomicInteger index = new AtomicInteger(0);

    /**
     * constructor
     *
     * @param maxBreadcrumbs
     * @param callbackState
     * @param logger
     */
    public BreadcrumbState(int maxBreadcrumbs, CallbackState callbackState, Logger logger) {
        this.maxBreadcrumbs = maxBreadcrumbs;
        this.callbackState = callbackState;
        this.logger = logger;
        store = new Breadcrumb[maxBreadcrumbs];
    }

    /**
     * add breadcrumb
     *
     * @param breadcrumb
     */
    public void add(Breadcrumb breadcrumb) {
        if (maxBreadcrumbs == 0 || !callbackState.runOnBreadcrumbTasks(breadcrumb, logger)) {
            return;
        }
        int position = getBreadcrumbIndex();
        store[position] = breadcrumb;
        Map<String, Object> map = breadcrumb.impl.metadata;
        if (map == null) {
            map = new HashMap<>();
        }
        updateState(
            new StateEvent.AddBreadcrumb(
                breadcrumb.impl.message,
                breadcrumb.impl.type,
                DateUtils.toIso8601(breadcrumb.impl.timestamp), map
            ));
    }

    /**
     * Retrieves the index in the ring buffer where the breadcrumb should be stored.
     */
    private int getBreadcrumbIndex() {
        while (true) {
            int currentValue = index.get() & validIndexMask;
            int nextValue = (currentValue + 1) % maxBreadcrumbs;
            if (index.compareAndSet(currentValue, nextValue)) {
                return currentValue;
            }
        }
    }

    /**
     * Creates a copy of the breadcrumbs in the order of their addition.
     *
     * @return breadcrumbs
     */
    public List<Breadcrumb> copy() {
        if (maxBreadcrumbs == 0) {
            return new ArrayList<>();
        }
        int tail = -1;
        while (tail == -1) {
            tail = index.getAndSet(-1);
        }

        try {
            Breadcrumb[] result = new Breadcrumb[maxBreadcrumbs];
            System.arraycopy(store, tail, result, 0, maxBreadcrumbs - tail);
            System.arraycopy(store, 0, result, maxBreadcrumbs - tail, tail - 0);
            List<Breadcrumb> breadcrumbList = new ArrayList<>();
            for (Breadcrumb breadcrumb : result) {
                if (breadcrumb != null) {
                    breadcrumbList.add(breadcrumb);
                }
            }
            return breadcrumbList;
        } finally {
            index.set(tail);
        }
    }

    @Override
    public void toStream(JsonStream writer) throws IOException {
        List<Breadcrumb> crumbs = copy();
        writer.beginArray();
        for (Breadcrumb crumb : crumbs) {
            crumb.toStream(writer);
        }
        writer.endArray();
    }
}
